/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Xceed.Silverlight.Data
{
  internal static class DataUtils
  {
    #region Static Fields

    //The "1" is for convenience. It will always create the same Ids for the same calls.
    private static Random RandomGenerator = new Random( 1 );

    #endregion Static Fields

    internal static int GetUnit( int moveOrFetchSize )
    {
      return DataUtils.GetUnit( Convert.ToInt64( moveOrFetchSize ) );
    }

    internal static int GetUnit( long moveOrFetchSize )
    {
      return ( moveOrFetchSize > 0 ) ? 1 : -1;
    }

    internal static bool IsQueryingForward( long moveSize, int fetchSize )
    {
      if( moveSize == 0 )
        return ( fetchSize >= 0 );

      return ( moveSize >= 0 );
    }

    internal static ICollection ConvertToNonGeneric<T>( ICollection<T> collection )
    {
      if( collection == null )
        return null;

      ICollection newCollection = collection as ICollection;

      return ( newCollection != null )
               ? newCollection
               : new List<T>( collection );
    }

    internal static Guid CreateCallId()
    {
      byte[] guidBytes = new byte[ 16 ];

      for( int i = 0; i < 16; i++ )
      {
        guidBytes[ i ] = ( byte )DataUtils.RandomGenerator.Next(
                                   byte.MinValue,
                                   byte.MaxValue );
      }

      return new Guid( guidBytes );
    }

    internal static bool IsNullableType( this Type type )
    {
      if( type == null )
        return false;

      return ( type.IsClass )
          || ( type.IsNullableStruct() );
    }

    internal static bool IsNullableStruct( this Type type )
    {
      if( type == null )
        return false;

      return ( type.IsGenericType )
          && ( type.GetGenericTypeDefinition() == typeof( Nullable<> ) );
    }

    internal static bool IsMethodImplemented( MethodInfo methodInfo, Type derivedType )
    {
      if( ( methodInfo == null ) || ( derivedType == null ) )
        return false;

      MethodInfo overridedMethodInfo = DataUtils.GetOverridedMethodDefinition( methodInfo, derivedType );
      if( ( overridedMethodInfo == null ) || ( overridedMethodInfo.IsAbstract ) )
        return false;

      return ( methodInfo.DeclaringType != overridedMethodInfo.DeclaringType );
    }

    internal static MethodInfo GetMethodDefinition<T1, TResult>( Expression<Func<T1, TResult>> lambda )
    {
      return DataUtils.GetMethodDefinition( ( Expression )lambda );
    }

    internal static MethodInfo GetMethodDefinition<T1, T2, TResult>( Expression<Func<T1, T2, TResult>> lambda )
    {
      return DataUtils.GetMethodDefinition( ( Expression )lambda );
    }

    internal static MethodInfo GetMethodDefinition<T1, T2, T3, TResult>( Expression<Func<T1, T2, T3, TResult>> lambda )
    {
      return DataUtils.GetMethodDefinition( ( Expression )lambda );
    }

    internal static MethodInfo GetMethodDefinition<T1, T2, T3, T4, TResult>( Expression<Func<T1, T2, T3, T4, TResult>> lambda )
    {
      return DataUtils.GetMethodDefinition( ( Expression )lambda );
    }

    internal static MethodInfo GetMethodDefinition<T1, T2, T3, T4, T5, TResult>( Expression<Func<T1, T2, T3, T4, T5, TResult>> lambda )
    {
      return DataUtils.GetMethodDefinition( ( Expression )lambda );
    }

    internal static MethodInfo GetMethodDefinition<T1, T2, T3, T4, T5, T6, TResult>( Expression<Func<T1, T2, T3, T4, T5, T6, TResult>> lambda )
    {
      return DataUtils.GetMethodDefinition( ( Expression )lambda );
    }

    internal static RelativeOffset CalculateRelativeOffset(
      RelativeOffset currentOffset,
      long absoluteOffsetDifference )
    {
      return DataUtils.CalculateRelativeOffset( currentOffset, absoluteOffsetDifference, true );
    }

    internal static RelativeOffset CalculateRelativeOffset(
      RelativeOffset currentOffset,
      long absoluteOffsetDifference,
      bool maxToZero )
    {
      RelativeOffset result = null;

      if( currentOffset != null )
      {
        if( currentOffset.ReferencePosition == ReferencePoint.BeforeFirst )
        {
          result = currentOffset + absoluteOffsetDifference;
        }
        else
        {
          result = currentOffset - absoluteOffsetDifference;
        }

        if( maxToZero && result.Value < 0 )
        {
          EstimateInt64 newOffset = new EstimateInt64( 0, true );

          result = new RelativeOffset( newOffset, result.ReferencePosition );
        }
      }

      return result;
    }

    internal static int FindSortedIndexForItem(
      IList dataList,
      bool listContainFirst,
      bool listContainLast,
      object item,
      Func<object, object, int> comparer,
      out bool isInsertionIndex )
    {
      if( dataList == null )
        throw new ArgumentNullException( "dataList" );

      if( item == null )
        throw new ArgumentNullException( "item" );

      if( comparer == null )
        throw new ArgumentNullException( "comparer" );

      int itemIndex = -1;
      isInsertionIndex = false;

      int i = 0;
      while( i < dataList.Count )
      {
        int compareResult = comparer( item, dataList[ i ] );

        // We stop our search here when the item is equal or greater.
        if( compareResult == 0 )
        {
          itemIndex = i;
          isInsertionIndex = false;
          break;
        }
        else if( compareResult < 0 )
        {
          if( i != 0 || listContainFirst )
          {
            itemIndex = i;
            isInsertionIndex = true;
          }
          break;
        }
        i++;
      }

      if( itemIndex == -1 && i == dataList.Count )
      {
        if( listContainLast )
        {
          itemIndex = dataList.Count;
          isInsertionIndex = true;
        }
      }

      return itemIndex;
    }

    private delegate void PositionComparer( object searchedPosition, object item, IDataHelper dataHelper, out int compareResult, out bool isItemPrecise );

    internal static Nullable<int> FindSortedIndexForOperation(
      IList dataList,
      bool listContainFirst,
      bool listContainLast,
      long expectedMoveCount,
      int expectedFetchCount,
      SourcePosition searchedPosition,
      IDataHelper dataHelper )
    {
      if( dataList == null )
        throw new ArgumentNullException( "dataList" );

      if( searchedPosition == null )
        throw new ArgumentNullException( "searchedPosition" );

      if( dataHelper == null )
        throw new ArgumentNullException( "dataHelper" );

      if( expectedMoveCount == 0 && expectedFetchCount == 0 )
        throw new ArgumentException( "Both expectedMoveCount and expectedFetchCount cannot be zero." );

      Nullable<int> index = null;

      if( searchedPosition.IsBeforeFirst )
      {
        if( listContainFirst )
          index = -1;
      }
      else if( searchedPosition.IsAfterLast )
      {
        if( listContainLast )
          index = dataList.Count;
      }
      else if( searchedPosition is ItemPosition )
      {
        object item = ( ( ItemPosition )searchedPosition ).Item;
        index = dataList.IndexOf( item );

        if( index == -1 )
        {
          index = DataUtils.FindSortedIndexForOperation(
            dataList,
            listContainFirst, listContainLast,
            expectedMoveCount, expectedFetchCount,
            item, dataHelper,
            DataUtils.CompareItemWithItem );
        }
      }
      else if( searchedPosition is SourcePosition )
      {
        index = DataUtils.FindSortedIndexForOperation(
          dataList,
          listContainFirst, listContainLast,
          expectedMoveCount, expectedFetchCount,
          searchedPosition, dataHelper,
          new PositionComparer( DataUtils.CompareSortPositionWithItem ) );
      }

      return index;
    }

    private static Nullable<int> FindSortedIndexForOperation(
      IList dataList,
      bool listContainFirst,
      bool listContainLast,
      long expectedMoveCount,
      int expectedFetchCount,
      object searchedPosition,
      IDataHelper dataHelper,
      PositionComparer comparer )
    {
      int lastCompareResult = 0;
      bool lastIsItemPrecise = false;
      Nullable<int> greaterIndex = null;
      Nullable<int> index = null;

      for( int i = 0; ( i < dataList.Count ); i++ )
      {
        comparer(
          searchedPosition,
          dataList[ i ],
          dataHelper,
          out lastCompareResult,
          out lastIsItemPrecise );

        // We stop our search here when the item is equal or greater.
        if( lastCompareResult == 0 )
        {
          index = i;
          break;
        }
        else if( lastCompareResult < 0 )
        {
          greaterIndex = i;
          break;
        }
      }

      if( index == null )
      {
        //No item "equal" were found. Use the "greater item" if found.
        if( greaterIndex != null )
        {
          if( greaterIndex.Value == 0 )
          {
            // If this is the first item, we do not consider it as "valid" because
            // the position could be anywhere before the item
            //
            // Except:
            // 1. The list contain the source's first item
            // 2. The SortPosition was "itemPrecise" with the first item
            if( !lastIsItemPrecise && !listContainFirst )
            {
              greaterIndex = null;
            }
          }
        }
        else
        {
          // If the SortPosition was greater than the last item
          // we consider it when:
          // 1. The list contain the source's last item
          // 2. The SortPosition was "itemPrecise" with the last item
          if( lastIsItemPrecise || listContainLast )
          {
            greaterIndex = dataList.Count;
          }
        }

        if( greaterIndex.HasValue )
        {
          //Ok the SortPosition is "just before" this index.
          //Place the index in relation to the expectedMoveCount and ExpectedFetchCount.
          if( expectedMoveCount > 0 )
          {
            index = greaterIndex - 1;
          }
          else if( expectedMoveCount < 0 )
          {
            index = greaterIndex;
          }
          else if( expectedFetchCount > 0 )
          {
            index = greaterIndex;
          }
          else if( expectedFetchCount < 0 )
          {
            index = greaterIndex - 1;
          }
          else
          {
            //This is supposed to be checked at method start.
            throw new DataInternalException();
          }
        }
      }

      return index;
    }

    private static void CompareItemWithItem(
      object sourcePosition,
      object item,
      IDataHelper dataHelper,
      out int compareResult,
      out bool isItemPrecise )
    {
      compareResult = dataHelper.Compare( sourcePosition, item );
      isItemPrecise = false; //useless for that version.
    }

    private static void CompareSortPositionWithItem(
      object sourcePosition,
      object item,
      IDataHelper dataHelper,
      out int compareResult,
      out bool isItemPrecise )
    {
      SortPosition sortPosition = ( SortPosition )sourcePosition;

      compareResult = DataUtils.CompareSortValues(
                     sortPosition.SortValues,
                     DataUtils.ExtractItemSortValues( item, sortPosition.SortingReference, dataHelper ),
                     sortPosition.SortingReference,
                     dataHelper );

      if( compareResult == 0 )
      {
        //If the SortPosition is ItemPrecise (contain all sort up to the primary key).
        //we return it as equal.
        isItemPrecise = ( dataHelper.CompleteSort.Length == sortPosition.SortValues.Count );

        //The StartPosition make the cut for equality.
        compareResult = ( sortPosition.StartPosition == StartPosition.Before ) ? -1 : 1;
      }
      else
      {
        isItemPrecise = false;
      }
    }

    private static IEnumerable<object> ExtractItemSortValues(
      object item,
      IEnumerable<SortDescription> sortDescriptions,
      IDataHelper dataHelper )
    {
      if( item == null )
        throw new ArgumentNullException( "item" );

      if( sortDescriptions == null )
        throw new ArgumentNullException( "sortDescriptions" );

      if( dataHelper == null )
        throw new ArgumentNullException( "dataHelper" );

      List<object> itemSortValues = new List<object>();

      foreach( SortDescription description in sortDescriptions )
      {
        object value;

        if( !dataHelper.TryGetMemberValue( description.PropertyName, item, out value ) )
          break;

        itemSortValues.Add( value );
      }

      return itemSortValues;
    }

    private static int CompareSortValues(
      IEnumerable<object> firstValues,
      IEnumerable<object> secondValues,
      IEnumerable<SortDescription> sortDescriptions,
      IDataHelper dataHelper )
    {
      if( firstValues == null )
        throw new ArgumentNullException( "firstValues" );

      if( secondValues == null )
        throw new ArgumentNullException( "secondValues" );

      if( sortDescriptions == null )
        throw new ArgumentNullException( "sortDescriptions" );

      if( dataHelper == null )
        throw new ArgumentNullException( "dataHelper" );

      IEnumerator<object> firstEnumerator = firstValues.GetEnumerator();
      IEnumerator<object> secondEnumerator = secondValues.GetEnumerator();
      int result = 0;

      foreach( SortDescription sortDescription in sortDescriptions )
      {
        bool firstMove = firstEnumerator.MoveNext();
        bool secondMove = secondEnumerator.MoveNext();

        if( firstMove && secondMove )
        {
          object firstValue = firstEnumerator.Current;
          object secondValue = secondEnumerator.Current;

          result = dataHelper.CompareValues( firstValue, secondValue, sortDescription );

          if( result != 0 )
            break;
        }
        else
        {
          break;
        }
      }

      return result;
    }

    public static bool ResolvePosition<T>(
      SourcePosition sourcePosition,
      bool considerRealPosition,
      out SourcePosition resolvedSourcePosition,
      out T resolvedItem,
      out long resolvedOffset,
      out bool approximateOffset,
      out Func<T, SourcePosition> positionResolver )
    {
      if( sourcePosition == null )
        throw new ArgumentNullException( "sourcePosition" );

      bool isBasedOnItem;

      if( sourcePosition is DummyPosition<T> )
      {
        DummyPosition<T> dummyPosition = ( DummyPosition<T> )sourcePosition;
        if( considerRealPosition && dummyPosition.RealPosition != null )
        {
          isBasedOnItem = DataUtils.ResolvePosition(
            dummyPosition.RealPosition,
            considerRealPosition,
            out resolvedSourcePosition,
            out resolvedItem,
            out resolvedOffset,
            out approximateOffset,
            out positionResolver );
        }
        else
        {
          if( dummyPosition.BasedOnItem )
          {
            positionResolver = null;
            isBasedOnItem = true;
            resolvedSourcePosition = null;
            resolvedItem = dummyPosition.BaseItem;
            resolvedOffset = dummyPosition.BaseOffset;
            approximateOffset = dummyPosition.BaseApproximateOffset;
          }
          else
          {
            long subOffset;
            bool subApproximateOffset;

            isBasedOnItem = DataUtils.ResolvePosition(
              dummyPosition.BaseSourcePosition,
              considerRealPosition,
              out resolvedSourcePosition,
              out resolvedItem,
              out subOffset,
              out subApproximateOffset,
              out positionResolver );

            resolvedOffset = dummyPosition.BaseOffset + subOffset;
            approximateOffset = dummyPosition.BaseApproximateOffset || subApproximateOffset;
          }
        }
      }
      else if( sourcePosition is DependentPosition<T> )
      {
        DependentPosition<T> dependentPosition = ( DependentPosition<T> )sourcePosition;
        isBasedOnItem = true;
        resolvedItem = dependentPosition.DependentItem;
        resolvedOffset = 0;
        resolvedSourcePosition = null;
        approximateOffset = false;
        positionResolver = dependentPosition.PositionResolver;
      }
      else
      {
        resolvedSourcePosition = sourcePosition;
        positionResolver = null;
        resolvedOffset = 0;
        approximateOffset = false;

        if( sourcePosition is ItemPosition )
        {
          resolvedItem = ( T )( ( ItemPosition )sourcePosition ).Item;
          isBasedOnItem = true;
        }
        else
        {
          resolvedItem = default( T );
          isBasedOnItem = false;
        }
      }

      return isBasedOnItem;
    }

    private static MethodInfo GetOverridedMethodDefinition( MethodInfo methodInfo, Type derivedType )
    {
      MethodInfo overridedMethodInfo = null;

      if( ( methodInfo != null )
        && ( methodInfo.IsVirtual || methodInfo.IsAbstract )
        && ( derivedType != null ) )
      {
        string methodName = methodInfo.Name;
        BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
        Type[] parameterTypes = ( from parameter in methodInfo.GetParameters()
                                  select parameter.ParameterType ).ToArray();

        overridedMethodInfo = derivedType.GetMethod(
                                methodName,
                                bindingFlags,
                                null,
                                parameterTypes,
                                null );
      }

      return overridedMethodInfo;
    }

    private static MethodInfo GetMethodDefinition( Expression lambda )
    {
      LambdaExpression expression = lambda as LambdaExpression;

      if( expression != null )
      {
        MethodCallExpression methodCall = expression.Body as MethodCallExpression;

        if( methodCall != null )
          return methodCall.Method;
      }

      return null;
    }
  }
}
